use std::fs::File;
use std::io::{self, BufReader, Error, Read};
use std::path::Path;
use suppaftp::{types::FileType, types::Mode, FtpError, FtpStream};

const ERR_NOT_CONN: &str = "ftp not connect or not login!!!";

pub struct Client {
    host: String,
    port: u32,
    username: String,
    password: String,
    client: Option<FtpStream>,
}

impl Client {
    pub fn new(host: String, port: u32, username: String, password: String) -> Self {
        Self {
            host,
            port,
            username,
            password,
            client: None,
        }
    }

    /// 建立 TCP 连接（不含登陆和 TLS）
    pub fn connect(&mut self) -> Result<(), FtpError> {
        let ftp_stream = FtpStream::connect(format!("{}:{}", self.host, self.port))?;
        self.client.replace(ftp_stream);
        Ok(())
    }

    /// 发送 QUIT 并关闭
    pub fn quit(&mut self) -> Result<(), FtpError> {
        if let Some(ftp_stream) = self.client.as_mut() {
            ftp_stream.quit()?;
            Ok(())
        } else {
            Err(FtpError::ConnectionError(Error::other(ERR_NOT_CONN)))
        }
    }

    /// USER / PASS 并尝试开启 UTF-8
    pub fn login(&mut self) -> Result<(), FtpError> {
        if let Some(ftp_stream) = self.client.as_mut() {
            ftp_stream.login(&self.username, &self.password)?;

            // 显式启用 UTF-8（若服务器支持）
            let _ = ftp_stream.opts("UTF8", Some("ON"));
            // 对于windows IIS 来说 必须设置为 EPSV
            // EPSV 在 Windows IIS 上更友好，suppaftp 在被动模式下自动尝试 EPSV→PASV
            ftp_stream.set_mode(Mode::ExtendedPassive);
            Ok(())
        } else {
            Err(FtpError::ConnectionError(Error::other(ERR_NOT_CONN)))
        }
    }

    /// 递归进入 / 创建 多级目录
    pub fn cwd(&mut self, to_path: &String) -> Result<(), FtpError> {
        if let Some(ftp_stream) = self.client.as_mut() {
            let mut current_path = vec![""]; // 以 / 开头，保持绝对路径效果
            for cpath in to_path.split('/') {
                if cpath.is_empty() {
                    continue;
                }
                current_path.push(cpath);
                let current = current_path.join("/");

                if let Err(e) = ftp_stream.cwd(&current) {
                    match e {
                        FtpError::UnexpectedResponse(_res) => {
                            //let msg = String::from_utf8_lossy(&res.body);
                            //println!("CWD Error: {}", msg);
                            // 目录不存在时，创建再进入
                            //if msg.contains("550") {
                            ftp_stream.mkdir(&current)?;
                            ftp_stream.cwd(&current)?;
                            //}
                        }
                        _ => return Err(e),
                    }
                }
            }
            Ok(())
        } else {
            Err(FtpError::ConnectionError(Error::other(ERR_NOT_CONN)))
        }
    }

    /// 上传文件（带进度回调）
    pub fn upload(
        &mut self,
        src_file_path: &Path,
        des_path: &String,
        des_filename: impl AsRef<str>,
        progress_callback: impl Fn(u8) + 'static,
    ) -> Result<(), FtpError> {
        let file = File::open(src_file_path).unwrap();
        let file_size = file.metadata().unwrap().len();
        let reader = BufReader::new(file);
        let mut progress_reader =
            ProgressReader::new(reader, file_size, Box::new(progress_callback));

        // 切换 / 递归创建目录
        self.cwd(des_path)?;

        if let Some(ftp_stream) = self.client.as_mut() {
            // 二进制模式
            ftp_stream.transfer_type(FileType::Binary)?;
            // EPSV 在 Windows IIS 上更友好，suppaftp 在被动模式下自动尝试 EPSV→PASV
            ftp_stream.put_file(des_filename.as_ref(), &mut progress_reader)?;
        }
        Ok(())
    }
}

impl Drop for Client {
    fn drop(&mut self) {
        if let Err(e) = self.quit() {
            eprintln!("关闭 FTP 连接时出错: {}", e);
        }
    }
}

/// 带进度回调的 Read 包装器
struct ProgressReader<R> {
    reader: R,
    total_size: u64,
    uploaded_size: u64,
    current_percent: u8,
    progress_callback: Box<dyn Fn(u8)>,
}

impl<R: Read> ProgressReader<R> {
    fn new(reader: R, total_size: u64, progress_callback: Box<dyn Fn(u8)>) -> Self {
        Self {
            reader,
            total_size,
            uploaded_size: 0,
            progress_callback,
            current_percent: 0,
        }
    }

    fn progress(&self) -> u8 {
        ((self.uploaded_size as f64 / self.total_size as f64) * 100.0) as u8
    }
}

impl<R: Read> Read for ProgressReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let bytes_read = self.reader.read(buf)?;
        self.uploaded_size += bytes_read as u64;

        let percent = self.progress();
        if percent > self.current_percent {
            self.current_percent = percent;
            (self.progress_callback)(percent);
        }
        Ok(bytes_read)
    }
}

// --------------------------- TESTS ---------------------------
#[cfg(test)]
mod tests {
    use super::*;

    // cargo test ftp_upload -- --nocapture
    #[test]
    fn ftp_upload() {
        let mut ftp = Client::new(
            "localhost".to_string(),
            2121,
            "xue".to_string(),
            "123456".to_string(),
        );
        ftp.connect().unwrap();
        ftp.login().unwrap();

        let r = ftp.upload(
            Path::new(
                "E:/BaiduNetdiskDownload/01.高数基础/01新文道版本/31.第五章-定积分的应用.mp4",
            ),
            &"/a/b/c/d".to_string(),
            "a.mp4",
            |percent| {
                println!("MY Uploaded: {}%", percent);
            },
        );
        println!("---> {:?}", r);
        assert!(r.is_ok());
    }
}
